<!-- 工序任务（生产任务） -->
<template>
  <div class="process-task-container">
    <!-- 筛选区域 -->
    <div class="filter-section">
      <div class="filter-item">
        <label>任务编号</label>
        <input v-model.trim="query.taskNo" type="text" placeholder="任务编号">
      </div>
      <div class="filter-item">
        <label>工单编号</label>
        <input v-model.trim="query.workOrderNo" type="text" placeholder="工单编号">
      </div>
      <div class="filter-item">
        <label>产品</label>
        <input v-model.trim="query.product" type="text" placeholder="产品">
      </div>
      <div class="filter-item">
        <label>工序号</label>
        <input v-model.number="query.processNo" type="number" min="0" placeholder="工序号">
      </div>
      <div class="filter-item">
        <label>工序编码</label>
        <input v-model.trim="query.processCode" type="text" placeholder="工序编码">
      </div>
      <div class="filter-item">
        <label>工序名称</label>
        <input v-model.trim="query.processName" type="text" placeholder="工序名称">
      </div>
      <div class="filter-item">
        <label>执行人</label>
        <input v-model.trim="query.username" type="text" placeholder="用户名">
      </div>
      <div class="filter-item">
        <label>状态</label>
        <select v-model="query.status">
          <option value="">全部</option>
          <option v-for="s in statusOptions" :key="s" :value="s">{{ s }}</option>
        </select>
      </div>

      <div class="filter-item">
        <label>计划开工</label>
        <input v-model="query.plannedStartDateBegin" type="date"> ~
        <input v-model="query.plannedStartDateEnd" type="date">
      </div>
      <div class="filter-item">
        <label>计划完工</label>
        <input v-model="query.plannedCompletionDateBegin" type="date"> ~
        <input v-model="query.plannedCompletionDateEnd" type="date">
      </div>

      <div class="filter-buttons">
        <button class="btn-primary" @click="onSearch" :disabled="loading">搜索</button>
        <button class="btn-secondary" @click="onReset" :disabled="loading">重置</button>
      </div>
    </div>

    <!-- 操作按钮 -->
    <div class="action-buttons">
      <button class="btn-primary" @click="openCreate">新建</button>
      <button class="btn-secondary" :disabled="!selectedIds.length" @click="batchDelete">批量删除</button>
    </div>

    <!-- 表格区域 -->
    <div class="table-section">
      <table>
        <thead>
          <tr>
            <th><input type="checkbox" :checked="allSelected" @change="toggleSelectAll($event)"></th>
            <th>任务编号</th>
            <th>状态</th>
            <th>关联工单</th>
            <th>产品</th>
            <th>工序号</th>
            <th>工序编码</th>
            <th>工序名称</th>
            <th>执行人</th>
            <th>计划开工日期</th>
            <th>计划完工日期</th>
            <th>完工数</th>
            <th>良品数</th>
            <th>进度(%)</th>
            <th>返工数/率</th>
            <th>报废数/率</th>
            <th>最后工序</th>
            <th style="width: 160px;">操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-if="!loading && !list.length">
            <td colspan="18" style="text-align:center;color:#909399;">暂无数据</td>
          </tr>
          <tr v-for="row in list" :key="row.id">
            <td>
              <input type="checkbox" :checked="selectedIds.includes(row.id)" @change="toggleSelect(row)">
            </td>
            <td>{{ row.taskNo || '-' }}</td>
            <td><span :class="['status-tag', getStatusClass(row.status)]">{{ row.status || '-' }}</span></td>
            <td>{{ row.workOrderNo || row.workOrderId || '-' }}</td>
            <td>{{ row.product || '-' }}</td>
            <td>{{ row.processNo ?? '-' }}</td>
            <td>{{ row.processCode || '-' }}</td>
            <td>{{ row.processName || '-' }}</td>
            <td>{{ row.username || '-' }}</td>
            <td>{{ formatDate(row.plannedStartDate) }}</td>
            <td>{{ formatDate(row.plannedCompletionDate) }}</td>
            <td>{{ row.numberCompleted ?? '-' }}</td>
            <td>{{ row.numberCompletedProducts ?? '-' }}</td>
            <td>{{ row.productionSchedule ?? '-' }}</td>
            <td>
              <span>{{ row.numberRework ?? '-' }}</span>
              <span v-if="row.reworkRate !== undefined"> / {{ row.reworkRate }}</span>
            </td>
            <td>
              <span>{{ row.scrappedQuantity ?? '-' }}</span>
              <span v-if="row.scrapRate !== undefined"> / {{ row.scrapRate }}</span>
            </td>
            <td>{{ row.finalProcess === true || row.finalProcess === 1 ? '是' : '否' }}</td>
            <td>
              <button class="btn-link" @click="viewDetail(row)">查看</button>
              <button class="btn-link" @click="openEdit(row)">编辑</button>
              <button class="btn-link danger" @click="confirmDelete(row)">删除</button>
            </td>
          </tr>
        </tbody>
      </table>
      <div v-if="loading" class="loading-tip">加载中...</div>
    </div>

    <!-- 底部区域 -->
    <div class="footer-section">
      <div class="footer-left">
        <input type="checkbox" :checked="allSelected" @change="toggleSelectAll($event)">
        <label>全选</label>
        <button class="btn-secondary" @click="exportCsv" :disabled="!list.length">导出</button>
      </div>
      <div class="pagination">
        <button @click="toPrevPage" :disabled="page.current <= 1">&lt;</button>
        <span class="page-info">第 {{ page.current }} / {{ totalPages || 1 }} 页</span>
        <button @click="toNextPage" :disabled="page.current >= totalPages">&gt;</button>
        <select v-model.number="page.size" @change="onSizeChange">
          <option :value="10">10条/页</option>
          <option :value="20">20条/页</option>
          <option :value="50">50条/页</option>
        </select>
        <span class="total">共 {{ page.total }} 条</span>
      </div>
    </div>

    <!-- 新建/编辑 弹窗 -->
    <div v-if="dialog.visible" class="dialog-mask">
      <div class="dialog">
        <div class="dialog-header">
          <span>{{ dialog.mode === 'create' ? '新建生产任务' : '编辑生产任务' }}</span>
          <button class="dialog-close" @click="closeDialog">×</button>
        </div>
        <div class="dialog-body">
          <div class="form-row">
            <label>状态</label>
            <select v-model="form.status">
              <option v-for="s in statusOptions" :key="s" :value="s">{{ s }}</option>
            </select>
          </div>
          <div class="form-row">
            <label>工单ID</label>
            <input v-model="form.workOrderId" type="text" placeholder="工单ID">
          </div>
          <div class="form-row">
            <label>产品</label>
            <input v-model="form.product" type="text" placeholder="产品">
          </div>
          <div class="form-row">
            <label>工序号</label>
            <input v-model.number="form.processNo" type="number" min="0" placeholder="工序号">
          </div>
          <div class="form-row">
            <label>工序ID</label>
            <input v-model="form.processId" type="text" placeholder="工序ID">
          </div>
          <div class="form-row">
            <label>工艺路线ID</label>
            <input v-model="form.processManagementId" type="text" placeholder="工艺路线ID">
          </div>
          <div class="form-row">
            <label>执行人ID</label>
            <input v-model="form.uId" type="text" placeholder="用户ID">
          </div>
          <div class="form-row">
            <label>计划开工</label>
            <input v-model="form.plannedStartDate" type="date">
          </div>
          <div class="form-row">
            <label>计划完工</label>
            <input v-model="form.plannedCompletionDate" type="date">
          </div>
          <div class="form-row">
            <label>是否最后工序</label>
            <select v-model="form.finalProcess">
              <option :value="false">否</option>
              <option :value="true">是</option>
            </select>
          </div>
          <div class="form-row grid-2">
            <div>
              <label>完工数</label>
              <input v-model.number="form.numberCompleted" type="number" min="0" placeholder="0">
            </div>
            <div>
              <label>良品数</label>
              <input v-model.number="form.numberCompletedProducts" type="number" min="0" placeholder="0">
            </div>
          </div>
          <div class="form-row grid-2">
            <div>
              <label>返工数</label>
              <input v-model.number="form.numberRework" type="number" min="0" placeholder="0">
            </div>
            <div>
              <label>返工率</label>
              <input v-model="form.reworkRate" type="text" placeholder="如 5% 或 0.05">
            </div>
          </div>
          <div class="form-row grid-2">
            <div>
              <label>报废数</label>
              <input v-model.number="form.scrappedQuantity" type="number" min="0" placeholder="0">
            </div>
            <div>
              <label>报废率</label>
              <input v-model="form.scrapRate" type="text" placeholder="如 2% 或 0.02">
            </div>
          </div>
          <div class="form-row">
            <label>生产进度(%)</label>
            <input v-model.number="form.productionSchedule" type="number" min="0" max="100" placeholder="0-100">
          </div>
        </div>
        <div class="dialog-footer">
          <button class="btn-secondary" @click="closeDialog">取消</button>
          <button class="btn-primary" :disabled="saving" @click="submitDialog">{{ saving ? '提交中...' : '确定' }}</button>
        </div>
      </div>
    </div>

    <!-- 详情弹窗 -->
    <div v-if="detail.visible" class="dialog-mask">
      <div class="dialog">
        <div class="dialog-header">
          <div class="dialog-title-wrap">
            <span>任务详情 - {{ detail.data.taskNo || '-' }}</span>
            <span v-if="detail.data.status" :class="['status-tag','status-sm', getStatusClass(detail.data.status)]" class="ml8">{{ detail.data.status }}</span>
          </div>
          <button class="dialog-close" @click="detail.visible = false">×</button>
        </div>
        <div class="dialog-body">
          <div class="detail-grid">
            <div class="detail-item" v-for="f in detailSchema" :key="f.key">
              <div class="detail-label">{{ f.label }}</div>
              <div class="detail-value">{{ formatField(detail.data, f) }}</div>
            </div>
          </div>
          <div class="detail-extra" v-if="Object.keys(detail.data || {}).length">
            <details>
              <summary>查看原始数据(JSON)</summary>
              <pre class="detail-json">{{ JSON.stringify(detail.data, null, 2) }}</pre>
            </details>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import {
  fetchProductionTasksPage,
  getProductionTaskById,
  createProductionTask,
  updateProductionTask,
  deleteProductionTask,
} from '@/api/processTask';

export default {
  name: 'ProcessTask',
  data() {
    return {
      loading: false,
      list: [],
      page: { current: 1, size: 10, total: 0 },
      query: {
        taskNo: '',
        status: '',
        workOrderNo: '',
        product: '',
        processNo: undefined,
        processCode: '',
        processName: '',
        processCodePc: '',
        processNamePn: '',
        username: '',
        plannedStartDateBegin: '',
        plannedStartDateEnd: '',
        plannedCompletionDateBegin: '',
        plannedCompletionDateEnd: '',
      },
      selectedIds: [],
      statusOptions: ['未开始', '进行中', '已完成'],
      dialog: { visible: false, mode: 'create' },
      form: {
        // create/update body
        id: undefined,
        status: '未开始',
        workOrderId: '',
        product: '',
        processNo: undefined,
        processId: '',
        processManagementId: '',
        uId: '',
        plannedStartDate: '',
        plannedCompletionDate: '',
        numberCompleted: 0,
        numberCompletedProducts: 0,
        productionSchedule: 0,
        numberRework: 0,
        reworkRate: '',
        scrappedQuantity: 0,
        scrapRate: '',
        finalProcess: false,
      },
      saving: false,
      detail: { visible: false, data: {}, loading: false },
      // 详情显示字段与顺序
      detailSchema: [
        { key: 'taskNo', label: '任务编号' },
        { key: 'status', label: '状态', type: 'status' },
        { key: 'workOrderNo', label: '工单编号' },
        { key: 'product', label: '产品' },
        { key: 'username', label: '执行人' },
        { key: 'processNo', label: '工序号' },
        { key: 'processCode', label: '工序编码' },
        { key: 'processName', label: '工序名称' },
        { key: 'processCodePc', label: '工艺路线编码' },
        { key: 'processNamePn', label: '工艺路线名称' },
        { key: 'plannedStartDate', label: '计划开工时间', type: 'datetime' },
        { key: 'plannedCompletionDate', label: '计划完工时间', type: 'datetime' },
        { key: 'numberCompleted', label: '完工数' },
        { key: 'numberCompletedProducts', label: '良品数' },
        { key: 'productionSchedule', label: '进度(%)' },
        { key: 'numberRework', label: '返工数' },
        { key: 'reworkRate', label: '返工率' },
        { key: 'scrappedQuantity', label: '报废数' },
        { key: 'scrapRate', label: '报废率' },
        { key: 'finalProcess', label: '是否最后工序', type: 'bool' },
      ],
    };
  },
  computed: {
    allSelected() {
      return this.list.length > 0 && this.selectedIds.length === this.list.length;
    },
    totalPages() {
      return Math.ceil((this.page.total || 0) / (this.page.size || 10));
    },
  },
  created() {
    this.loadData();
  },
  methods: {
    // 详情字段格式化
    formatField(row, f) {
      const val = row ? row[f.key] : undefined;
      if (f.type === 'bool') return (val === true || val === 1) ? '是' : (val === false || val === 0) ? '否' : '-';
      if (f.type === 'date') return this.formatDate(val);
      if (f.type === 'datetime') return this.formatDateTime(val);
      return (val === undefined || val === null || val === '') ? '-' : val;
    },
    async loadData() {
      this.loading = true;
      try {
        const params = { pageNum: this.page.current, pageSize: this.page.size, ...this.cleanQuery(this.query) };
        const { data } = await fetchProductionTasksPage(params);
        // 兼容 MyBatis-Plus IPage，并将下划线命名转换为驼峰，避免列表渲染不到
        const records = Array.isArray(data?.records) ? data.records.map(r => this.camelizeKeys(r)) : [];
        this.list = records;
        this.page.total = data?.total ?? 0;
        this.page.size = data?.size ?? this.page.size;
        this.page.current = data?.current ?? this.page.current;
        // 重置选择
        this.selectedIds = [];
      } catch (e) {
        console.error('加载列表失败:', e);
        alert('加载列表失败');
      } finally {
        this.loading = false;
      }
    },
    onSearch() {
      this.page.current = 1;
      this.loadData();
    },
    onReset() {
      this.query = {
        taskNo: '', status: '', workOrderNo: '', product: '', processNo: undefined, processCode: '', processName: '', processCodePc: '', processNamePn: '', username: '',
        plannedStartDateBegin: '', plannedStartDateEnd: '', plannedCompletionDateBegin: '', plannedCompletionDateEnd: '',
      };
      this.onSearch();
    },
    onSizeChange() {
      this.page.current = 1;
      this.loadData();
    },
    toPrevPage() {
      if (this.page.current > 1) {
        this.page.current -= 1;
        this.loadData();
      }
    },
    toNextPage() {
      if (this.page.current < this.totalPages) {
        this.page.current += 1;
        this.loadData();
      }
    },
    toggleSelect(row) {
      const idx = this.selectedIds.indexOf(row.id);
      if (idx >= 0) this.selectedIds.splice(idx, 1);
      else this.selectedIds.push(row.id);
    },
    toggleSelectAll(e) {
      const checked = e.target.checked;
      this.selectedIds = checked ? this.list.map(r => r.id) : [];
    },
    getStatusClass(status) {
      switch (status) {
        case '未开始': return 'status-pending';
        case '进行中': return 'status-in-progress';
        case '已完成': return 'status-completed';
        default: return '';
      }
    },
    formatDate(val) {
      if (!val) return '-';
      try {
        // 支持字符串日期或时间戳
        const d = new Date(val);
        if (Number.isNaN(d.getTime())) return String(val);
        const y = d.getFullYear();
        const m = String(d.getMonth() + 1).padStart(2, '0');
        const day = String(d.getDate()).padStart(2, '0');
        return `${y}-${m}-${day}`;
      } catch (e) {
        return String(val);
      }
    },
    exportCsv() {
      if (!this.list.length) return;
      const headers = ['任务编号','状态','工单编号','产品','工序号','工序编码','工序名称','执行人','计划开工','计划完工','完工数','良品数','进度','返工数','返工率','报废数','报废率','最后工序'];
      const rows = this.list.map(r => [
        r.taskNo || '', r.status || '', r.workOrderNo || r.workOrderId || '', r.product || '', r.processNo ?? '', r.processCode || '', r.processName || '', r.username || '',
        this.formatDate(r.plannedStartDate), this.formatDate(r.plannedCompletionDate), r.numberCompleted ?? '', r.numberCompletedProducts ?? '', r.productionSchedule ?? '',
        r.numberRework ?? '', r.reworkRate ?? '', r.scrappedQuantity ?? '', r.scrapRate ?? '', (r.finalProcess === true || r.finalProcess === 1) ? '是' : '否'
      ]);
      const csv = [headers.join(','), ...rows.map(r => r.map(v => `"${String(v).replace(/"/g, '""')}"`).join(','))].join('\n');
      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `生产任务_${Date.now()}.csv`;
      a.click();
      URL.revokeObjectURL(url);
    },
    openCreate() {
      this.dialog.mode = 'create';
      this.dialog.visible = true;
      this.form = {
        id: undefined,
        status: '未开始',
        workOrderId: '',
        product: '',
        processNo: undefined,
        processId: '',
        processManagementId: '',
        uId: '',
        plannedStartDate: '',
        plannedCompletionDate: '',
        numberCompleted: 0,
        numberCompletedProducts: 0,
        productionSchedule: 0,
        numberRework: 0,
        reworkRate: '',
        scrappedQuantity: 0,
        scrapRate: '',
        finalProcess: false,
      };
    },
    async openEdit(row) {
      this.dialog.mode = 'edit';
      this.dialog.visible = true;
      // 先用当前行填充，确保可编辑
      this.form = { ...this.form, ...row };
      try {
        if (row?.id) {
          const { data } = await getProductionTaskById(row.id);
          const camel = this.camelizeKeys(data || {});
          this.form = { ...this.form, ...camel };
        }
      } catch (e) {
        console.error('获取详情失败(使用列表数据回填)', e);
        // 不打断用户编辑，不再弹窗打扰
      }
    },
    async viewDetail(row) {
      this.detail.visible = true;
      // 先展示当前行数据，提升响应
      this.detail.data = row || {};
      try {
        if (row?.id) {
          const { data } = await getProductionTaskById(row.id);
          // 详情接口返回的字段也做驼峰化
          this.detail.data = this.camelizeKeys(data || {});
        }
      } catch (e) {
        console.error('加载详情失败', e);
      }
    },
    closeDialog() {
      if (this.saving) return;
      this.dialog.visible = false;
    },
    async submitDialog() {
      if (this.saving) return;
      this.saving = true;
      try {
        if (this.dialog.mode === 'create') {
          const { data } = await createProductionTask({ ...this.form });
          if (data && data.success) {
            alert(`创建成功，任务编号：${data.taskNo || ''}`);
            this.dialog.visible = false;
            this.onSearch();
          } else {
            alert('创建失败');
          }
        } else {
          const payload = { ...this.form };
          if (!payload.id) {
            alert('缺少ID，无法更新');
            return;
          }
          const { data } = await updateProductionTask(payload);
          if (data && data.success) {
            alert('更新成功');
            this.dialog.visible = false;
            this.loadData();
          } else {
            alert('更新失败');
          }
        }
      } catch (e) {
        console.error('提交失败', e);
        alert('提交失败');
      } finally {
        this.saving = false;
      }
    },
    async confirmDelete(row) {
      if (!row?.id) return;
      if (!window.confirm(`确认删除任务：${row.taskNo || row.id} ？`)) return;
      try {
        const { data } = await deleteProductionTask(row.id);
        if (data && data.success) {
          alert('删除成功');
          this.loadData();
        } else {
          alert('删除失败');
        }
      } catch (e) {
        console.error('删除失败', e);
        alert('删除失败');
      }
    },
    async batchDelete() {
      if (!this.selectedIds.length) return;
      if (!window.confirm(`确认删除选中的 ${this.selectedIds.length} 条记录？`)) return;
      try {
        // 后端仅提供单删，前端串行调用
        for (const id of this.selectedIds) {
          // eslint-disable-next-line no-await-in-loop
          await deleteProductionTask(id);
        }
        alert('批量删除完成');
        this.loadData();
      } catch (e) {
        console.error('批量删除失败', e);
        alert('批量删除失败');
      }
    },
    cleanQuery(q) {
      const r = {};
      Object.keys(q).forEach(k => {
        const v = q[k];
        if (v !== '' && v !== undefined && v !== null) r[k] = v;
      });
      return r;
    },
    // 将对象的下划线命名转换为驼峰命名（深度转换）
    camelizeKeys(input) {
      if (Array.isArray(input)) {
        return input.map(i => this.camelizeKeys(i));
      }
      if (input && typeof input === 'object') {
        const out = {};
        Object.keys(input).forEach(key => {
          const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
          out[camelKey] = this.camelizeKeys(input[key]);
        });
        return out;
      }
      return input;
    },
    // 状态显示（兼容数字/字符串）
    getStatusText(val) {
      if (val === null || val === undefined || val === '') return '-';
      const map = { 0: '未开始', 1: '进行中', 2: '已完成' };
      if (typeof val === 'number') return map[val] ?? String(val);
      // 字符串数字
      if (/^\d+$/.test(val)) return map[Number(val)] ?? val;
      return val;
    },
    // 带时间的格式化
    formatDateTime(val) {
      if (!val) return '-';
      const d = new Date(val);
      if (Number.isNaN(d.getTime())) return String(val);
      const y = d.getFullYear();
      const m = String(d.getMonth() + 1).padStart(2, '0');
      const day = String(d.getDate()).padStart(2, '0');
      const hh = String(d.getHours()).padStart(2, '0');
      const mm = String(d.getMinutes()).padStart(2, '0');
      const ss = String(d.getSeconds()).padStart(2, '0');
      return `${y}-${m}-${day} ${hh}:${mm}:${ss}`;
    },
  },
};
</script>

<style scoped>
.process-task-container {
  padding: 20px;
  background-color: #f5f7fa;
}

.filter-section {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  margin-bottom: 20px;
  align-items: center;
}

.filter-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.filter-item label {
  font-size: 14px;
  white-space: nowrap;
}

.filter-item input,
.filter-item select {
  padding: 8px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  width: 180px;
}

.filter-buttons { margin-left: auto; }

.btn-primary {
  background-color: #409eff;
  color: #fff;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  transition: all .15s ease;
}
.btn-primary:hover { filter: brightness(1.05); }

.btn-secondary {
  background-color: #fff;
  color: #606266;
  border: 1px solid #dcdfe6;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  margin-left: 10px;
  transition: all .15s ease;
}
.btn-secondary:hover { background: #f7f7f7; }

.btn-link {
  background: none;
  border: none;
  color: #409eff;
  cursor: pointer;
  padding: 4px 6px;
}
.btn-link.danger { color: #f56c6c; }

.action-buttons { margin-bottom: 20px; }

.table-section {
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  position: relative;
}

.loading-tip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,0.6);
  font-size: 14px;
}

table { width: 100%; border-collapse: collapse; }

th, td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #ebeef5;
  vertical-align: top;
}

th { background-color: #f8f9fa; }

.status-tag {
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.03) inset;
}

.status-pending { background-color: #fdf6ec; color: #e6a23c; }
.status-in-progress { background-color: #f0f9eb; color: #67c23a; }
.status-completed { background-color: #f4f4f5; color: #909399; }

.footer-section {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 20px;
  background-color: #fff;
  padding: 10px 20px;
  border-radius: 8px;
}

.footer-left { display: flex; align-items: center; gap: 8px; }

.pagination { display: flex; align-items: center; gap: 8px; }

.pagination button {
  padding: 6px 12px;
  border: 1px solid #dcdfe6;
  background-color: #fff;
  cursor: pointer;
  border-radius: 4px;
}

.pagination .page-info { color: #606266; }

.pagination select { padding: 6px; border: 1px solid #dcdfe6; border-radius: 4px; }

.pagination .total { color: #909399; }

/* 弹窗 */
.dialog-mask {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  -webkit-backdrop-filter: blur(1px);
  backdrop-filter: blur(1px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}

.dialog {
  width: min(900px, 92vw);
  max-height: 85vh;
  background: #fff;
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 30px rgba(0,0,0,0.12);
}

.dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid #ebeef5;
  font-weight: 700;
  font-size: 16px;
}

.dialog-close {
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: #909399;
}
.dialog-close:hover { color: #606266; }

.dialog-body { padding: 16px; overflow: auto; }

.form-row { display: grid; grid-template-columns: 120px 1fr; gap: 8px; align-items: center; margin-bottom: 12px; }
.form-row.grid-2 { grid-template-columns: 120px 1fr 120px 1fr; }
.form-row input, .form-row select { padding: 8px; border: 1px solid #dcdfe6; border-radius: 4px; }

.dialog-footer { padding: 12px 16px; border-top: 1px solid #ebeef5; display: flex; justify-content: flex-end; gap: 8px; }

/* 详情 JSON 视觉优化 */
.detail-json {
  background: #f6f8fa;
  border: 1px solid #eaecef;
  border-radius: 8px;
  padding: 16px 20px;
  color: #24292e;
  font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Monaco, 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 60vh;
  overflow: auto;
  box-shadow: inset 0 0 0 1px rgba(27,31,36,0.04);
}
.detail-json::-webkit-scrollbar { height: 10px; width: 10px; }
.detail-json::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 6px; }
.detail-json::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }

/* 详情弹窗布局 */
.dialog-title-wrap { display: flex; align-items: center; gap: 8px; }
.ml8 { margin-left: 8px; }
.status-sm { font-size: 12px; padding: 2px 6px; }

.detail-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px 24px;
}
@media (max-width: 680px) {
  .detail-grid { grid-template-columns: 1fr; }
}

.detail-item {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 8px;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid #f0f0f0;
  border-radius: 6px;
  background: #fafafa;
}
.detail-label { color: #909399; }
.detail-value { color: #303133; word-break: break-all; }

.detail-extra { margin-top: 12px; }
.detail-loading { text-align: center; color: #909399; padding: 20px 0; }

</style>
